package p.ithorns.framework.crypto.advice;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import p.ithorns.framework.crypto.core.Crypto;
import p.ithorns.framework.crypto.core.CryptoProperties;
import p.ithorns.framework.crypto.core.NeedCrypto;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * DecryptRequestBodyAdvice
 *
 * @author ithorns
 * @version 1.0
 * @date 2019/5/24 11:21
 */
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    private final static Logger log = LoggerFactory.getLogger(DecryptRequestBodyAdvice.class);

    private final static String REQUEST_DATA_KEY = "ciphertext";

    @Autowired
    private Crypto crypto;

    @Autowired
    private CryptoProperties properties;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        if (NeedCrypto.needDecrypt(methodParameter)) {
            try {
                return new DecryptHttpInputMessage(httpInputMessage);
            } catch (Exception e) {
                log.error("数据解密失败", e);
            }
        }
        return httpInputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    class DecryptHttpInputMessage implements HttpInputMessage {

        private volatile HttpHeaders headers;

        private volatile InputStream body;

        public DecryptHttpInputMessage(){}

        public DecryptHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
            String privateKey = properties.getPrivateKey();
            if (StringUtils.isEmpty(privateKey)) {
                throw new IllegalArgumentException("privateKey is null");
            }

            String charset = properties.getCharset();
            headers = inputMessage.getHeaders();
            String content = IOUtils.toString(inputMessage.getBody(), charset);

            // 获取请求body中的data密文
            try {
                ObjectMapper mapper = new ObjectMapper();
                Map<String,String> map = mapper.readValue(content,new TypeReference<Map<String, String>>(){});
                /* {"ciphertext": "decryptBody"} 形式内容 例-{"a": "xxx", "b": bbb}使用publickey加密然后Base64的内容 */
                if (map.containsKey(REQUEST_DATA_KEY)) {
                    content = map.get(REQUEST_DATA_KEY);
                } else {
                    // 没有经过加密的map内容，直接获取body
                    this.body = IOUtils.toInputStream(content, charset);
                    return ;
                }
            } catch (Exception e) {
                log.debug("Map解析失败，以String方式处理");
            }
            String decryptBody = crypto.decrypt(content, privateKey, charset);
            this.body = IOUtils.toInputStream(decryptBody, charset);
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}
